package nebenlaeufig;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class NParser {

	private ArrayList<Integer> nodes = new ArrayList<Integer>();
	private Map<Integer,Integer> values = new HashMap<Integer,Integer>();
	private Map<Integer,ArrayList<Integer>> relations = new HashMap<Integer,ArrayList<Integer>>();
	
	/**
	 * Initializates the parsing of the XML-graph
	 * @param filePaths array with all filepaths of the XML-docs
	 * @throws Exception if XML-doc has wrong format
	 */
	public NParser(String[] filePaths) throws Exception {
		//to parse every XML-file
		for(int i=0;i<filePaths.length;i++)
			readDocument(filePaths[i]);
		
		//counter to check if there are source nodes which do not exists in the node arraylist
		int count = 0;
		Integer node, aim;
		ArrayList<Integer> actual;
		//iterate over all nodes to check if there are nodes in the relation destination which do not exists in the node arraylist
		for(int i=0;i<nodes.size();i++){
			node = nodes.get(i);
			actual = relations.get(node);
			//if there is no destination-list of this node, continue with the next node
			if(actual==null)
				continue;
			else{
				count++;
				for(int j=0;j<actual.size();j++){
					aim = actual.get(j);
					if(!nodes.contains(aim))
						System.exit(-1);
				}
			}
		}
		if(count!=relations.size())
			System.exit(-1);
	}
	
	/**
	 * to get the nodes after parsing
	 * @return the list of nodes
	 */
	public ArrayList<Integer> getNodes(){
		return nodes;
	}
	
	/**
	 * to get the values after parsing
	 * @return the list of values
	 */
	public Map<Integer,Integer> getValues(){
		return values;
	}
	
	/**
	 * to get the relations after parsing
	 * @return the list of relations
	 */
	public Map<Integer,ArrayList<Integer>> getRelations(){
		return relations;
	}
	
	/**
	 * the main parsing method
	 * @param filePath where the XML-doc is saved
	 * @throws Exception if XML-doc has wrong format or if DOMParser throws an exception
	 */
	private void readDocument(String filePath) throws Exception {
		//initialize the file-reader
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
		DocumentBuilder builder = factory.newDocumentBuilder(); 
		Document document = builder.parse( new File(filePath) );
		
		//this are all main blocks in the file
		NodeList list = document.getChildNodes();
		Node node;
		//iterate over all main blocks to find the graph block and to check if there are forbidden block
		for(int i=0;i<list.getLength();i++){
			node = list.item(i);
			//switch to do the right thing for the actual block
			switch(calcTag(node.getNodeName())){
			case GRAPH: readGraph(node); break;
			case TEXT: break;
			default: System.exit(-1);
			}
		}
	}
	
	/**
	 * 
	 * @param graph the node with the name "graph"
	 * @throws IOException if the XML-file has wrong format
	 */
	private void readGraph(Node graph) throws IOException{
		NodeList list = graph.getChildNodes();
		Node node;
		//iterate over all children blocks of the graph
		for(int i=0;i<list.getLength();i++){
			node = list.item(i);
			switch(calcTag(node.getNodeName())){
			case NODE: readNode(node); break;
			case EDGE: readEdge(node); break;
			case TEXT: break;
			default: System.exit(-1);
			}
		}
	}
	
	/**
	 * read the id and value of a node
	 * @param node the node from the tree
	 * @throws IOException if XML-doc has wrong format
	 */
	private void readNode(Node node) throws IOException{
		NodeList childs = node.getChildNodes();
		Node idN = null,
			valueN = null,
			temp;
		//counter to check if there is only one id and only one capacity block
		int n = 0,
			c = 0;
		//iterate over the children of the node block to extract the id and the value of the node
		for(int i=0;i<childs.getLength();i++){
			temp = childs.item(i);
			switch(calcTag(temp.getNodeName())){
			case ID: idN=temp; n++; break;
			case CAPACITY: valueN=temp; c++; break;
			case TEXT: break;
			default: System.exit(-1);
			}
		}
		if( n!=1 || c!=1)
			throw new IOException("Parser/readNode: XML-doc has wrong format!");
		if(idN==null || valueN==null)
			throw new IOException("Parser/readNode: XML-doc has wrong format!");
		n = 0;
		c = 0;
		
		int id = new Integer(idN.getTextContent().trim()).intValue(),
			value = new Integer(valueN.getTextContent().trim()).intValue();
		
		//if the nodes arraylist already contains a node with this id
		if(nodes.contains(id)){
			if(values.get(id)!=value)
				System.exit(-1);
		}
		else{	
			nodes.add(id);
			values.put(id,value);
		}
	}
	
	/**
	 * read the source and destination of a relation
	 * @param node the node from the tree
	 * @throws IOException if XML-doc has wrong format
	 */
	private void readEdge(Node node) throws IOException{
		NodeList childs = node.getChildNodes();
		Node srcidN = null,
			dstidN = null,
			temp;
		//counter to check if there is only one srcid and only one dstid block
		int s = 0,
			d = 0;
		for(int i=0;i<childs.getLength();i++){
			temp = childs.item(i);
			switch(calcTag(temp.getNodeName())){
			case SRCID: srcidN=temp; s++; break;
			case DSTID: dstidN=temp; d++; break;
			case TEXT: break;
			default: System.exit(-1);
			}
		}
		if(s!=1 || d!=1)
			throw new IOException("Parser/readEdge: XML-doc has wrong format!");
		if(srcidN==null || dstidN==null)
			throw new IOException("Parser/readEdge: XML-doc has wrong format!");
		s = 0;
		d = 0;
		
		int srcid = new Integer(srcidN.getTextContent().trim()).intValue(),
			dstid = new Integer(dstidN.getTextContent().trim()).intValue();
		
		//if the relations map already contains a relation for this source node add the destination node in the destination arraylist
		if(relations.containsKey(srcid))
			relations.get(srcid).add(dstid);
		//else create a new arraylist with the destination node and add source node and the new arraylist as new mapping in the relations map
		else{
			ArrayList<Integer> curr = new ArrayList<Integer>();
			curr.add(dstid);
			relations.put(srcid, curr);
		}
	}
	
	/**
	 * for switch over the nodes from the tree
	 */
	private enum Tag {
		GRAPH, NODE, ID, CAPACITY, EDGE, SRCID, DSTID, TEXT, OTHER
	}
	
	/**
	 * calculate the tag for a specific string from a node
	 * @param line the string
	 * @return the tag
	 */
	private  Tag calcTag(String line){
		if (line.startsWith("graph"))
			return Tag.GRAPH;
		else if(line.startsWith("#text"))
			return Tag.TEXT;
		else if (line.startsWith("node"))
			return Tag.NODE;
		else if (line.startsWith("id"))
			return Tag.ID;
		else if (line.startsWith("capacity"))
			return Tag.CAPACITY;
		else if (line.startsWith("edge"))
			return Tag.EDGE;
		else if (line.startsWith("srcid"))
			return Tag.SRCID;
		else if (line.startsWith("dstid"))
			return Tag.DSTID;
		else return Tag.OTHER;
	}
}
